/*
 *  Copyright (C) 2021  赵桐
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "SerialPort.h"

SerialPort::SerialPort(const char* port_name)
    : serial_fd_(-1)
    , port_name_(port_name)
    , baudrate_(B115200)
    , data_bits_(8)
    , parity_bits_('N')
    , stop_bits_(1)
{
  
}

SerialPort::~SerialPort()
{
    CloseDevice();
    printf("serial port %s destoryed\n",port_name_);
}

bool SerialPort::Init()
{
    if (nullptr == port_name_)
        port_name_ = "/dev/ttyUSB0";
    if (OpenDevice() && ConfigDevice()) {
        fd_set serial_fd_set_;
        FD_ZERO(&serial_fd_set_);
        FD_SET(serial_fd_, &serial_fd_set_);
        printf("Serial port init success.\n");
        return true;
    } else {
        printf("Failed to start serial port.\n");
        CloseDevice();
        return false;
    }
}

bool SerialPort::OpenDevice()
{
#ifdef __arm__
    serial_fd_ = open(port_name_, O_RDWR);
#else
    serial_fd_ = open(port_name_, O_RDWR | O_NOCTTY);
#endif
    if (serial_fd_ < 0)
        return false;
    else
        return true;
}

bool SerialPort::CloseDevice()
{
    close(serial_fd_);
    serial_fd_ = -1;
    return true;
}

bool SerialPort::ConfigDevice()
{
    struct termios new_termios_;
    memset(&new_termios_, 0, sizeof(new_termios_));

    /* config the size of char */
    new_termios_.c_cflag |= CLOCAL | CREAD;
    new_termios_.c_cflag &= ~CSIZE;

    /* config data bit */
    switch (data_bits_) {
    case 7:
        new_termios_.c_cflag |= CS7;
        break;
    case 8:
        new_termios_.c_cflag |= CS8;
        break;
    default:
        new_termios_.c_cflag |= CS8;
        break; //8N1 default config
    }

    /* config the parity bit */
    switch (parity_bits_) {
    /* odd */
    case 'O':
    case 'o':
        new_termios_.c_cflag |= PARENB;
        new_termios_.c_cflag |= PARODD;
        break;
    /* even */
    case 'E':
    case 'e':
        new_termios_.c_cflag |= PARENB;
        new_termios_.c_cflag &= ~PARODD;
        break;
    /* none */
    case 'N':
    case 'n':
        new_termios_.c_cflag &= ~PARENB;
        break;
    default:
        new_termios_.c_cflag &= ~PARENB;
        break; //8N1 default config
    }

    /* config baudrate */
    cfsetispeed(&new_termios_, baudrate_);
    cfsetospeed(&new_termios_, baudrate_);

    /* config stop bit */
    if (stop_bits_ == 1)
        new_termios_.c_cflag &= ~CSTOPB;
    else if (stop_bits_ == 2)
        new_termios_.c_cflag |= CSTOPB;
    else
        new_termios_.c_cflag &= ~CSTOPB; //8N1 default config

    /* config waiting time & min number of char */
    new_termios_.c_cc[VTIME] = 1;
    new_termios_.c_cc[VMIN] = 18;

    /* using the raw data mode */
    new_termios_.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    new_termios_.c_oflag &= ~OPOST;

    /* flush the hardware fifo */
    tcflush(serial_fd_, TCIFLUSH);

    /* activite the configuration */
    if ((tcsetattr(serial_fd_, TCSANOW, &new_termios_)) != 0)
        return false;
    return true;
}

int SerialPort::Read(uint8_t* buf, int len)
{
    int ret = -1;
    if (NULL == buf) {
        return -1;
    } else {
        ret = read(serial_fd_, buf, len);
        while (ret == 0)
        {
            printf("Connection closed, try to reconnect.\n");
            while (!Init())
            {
                usleep(500000);
            }
            printf("Reconnect Success.\n");
            ret = read(serial_fd_, buf, len);
        }
        return ret;
    }
}

int SerialPort::Write(const uint8_t* buf, int len)
{
    return write(serial_fd_, buf, len);
}

void SerialPort::SetPortName(const char * port_name)
{
    this->port_name_ = port_name;
    printf("Port name has been changed to %s\n",this->port_name_);

}

void SerialPort::Destory(){
    CloseDevice();
}


